﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using System.Windows.Forms;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using TfsBugger.Util;
using TfsBugger.Util.Ui;

namespace TfsBugger.Forms
{
    public partial class CheckInBuggerForm : Form
    {
        #region Nested Decls
        private delegate void ChangeSetChecker(string projName);
        #endregion


        public CheckInBuggerForm()
        {
            InitializeComponent();
        }


        #region Fields
        private int m_lastChangeSetId = 0;
        private List<Changeset> m_newChangeSets = new List<Changeset>();
        private ManualResetEvent m_stopFlag = new ManualResetEvent(false);
        private ManualResetEvent m_workingFlag = new ManualResetEvent(false);
        #endregion


        #region Event Handlers 
        private void OnFormLoad(object sender, EventArgs e)
        {
            foreach (Project proj in Program.SelectedProjects)
                this.cboProjects.Items.Add(proj.Name);
        }


        private void OnStartButtonClick(object sender, EventArgs e)
        {
            this.btnStart.Enabled = false;
            this.btnStop.Enabled = true;
            this.lblStatus.Text = "Bugging server for check-ins...";
            this.lblStatus.Visible = true;
            ChangeSetChecker work = DoAsyncWork;
            IAsyncResult result = work.BeginInvoke(this.cboProjects.SelectedItem.ToString(), OnAsyncWorkCompleted, null);
        }

        private void OnStopButtonClick(object sender, EventArgs e)
        {
            this.lblStatus.Text = "Stopping...";
            m_stopFlag.Set();
        }

        private void OnSelectedProjChanged(object sender, EventArgs e)
        {
            if (null != this.cboProjects.SelectedItem)
            {
                this.btnStart.Enabled = true;
                this.btnStop.Enabled = false;
            }
        }


        private void OnAsyncWorkCompleted(IAsyncResult result)
        {
            this.DoInvoke(() =>
                {
                    this.lblStatus.Visible = false;
                    this.btnStart.Enabled = true;
                    this.btnStop.Enabled = false;
                });
            
            var r = (AsyncResult)result;
            try
            {
                ((ChangeSetChecker)r.AsyncDelegate).EndInvoke(result);
            }
            catch (Exception ex)
            {
                this.DoInvoke(() =>
                    {
                        MessageBox.Show(
                            string.Format("An error occurred while trying to bug the server.  Details: \n\n{0}", ex.Message),
                            "Bugging Error",
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Error);
                    });                
            }
        }


        private void OnCloseButtonClick(object sender, EventArgs e)
        {
            m_stopFlag.Set();
            using (WaitScope wait = new WaitScope(this))
            {
                while (m_workingFlag.WaitOne(0, false))
                    Application.DoEvents(); 
            }

            Close();
        }        
        #endregion 


        #region Methods
        private void DoAsyncWork(string projName)
        {
            m_workingFlag.Set();
            lock (m_newChangeSets)
                m_newChangeSets.Clear();

            TeamFoundationServer tfs = Program.TfsServer;
            if (null != tfs && tfs.HasAuthenticated)
            {
                var vcs = (VersionControlServer)tfs.GetService(typeof(VersionControlServer));
                TeamProject proj = vcs.GetTeamProject(projName);
                if (null == proj)
                {
                    m_stopFlag.Reset();
                    m_workingFlag.Reset();
                    throw new InvalidOperationException(string.Format("The project with name '{0}' could not be found in source control.", projName));
                }

                if (m_lastChangeSetId == 0)
                    m_lastChangeSetId = vcs.GetLatestChangesetId();

                while (true)
                {
                    if (m_stopFlag.WaitOne(0, false))
                        break;

                    //Only poll the VC server once every 10 seconds
                    Thread.Sleep(10000);
                    int latest = vcs.GetLatestChangesetId();
                    if (latest != m_lastChangeSetId)
                    {
                        //If there is a new changeset on the source control server, 
                        //query for change items for the selected project 
                        //and add to the collection if any found that match 
                        //the latest changeset id
                        m_lastChangeSetId = latest;
                        Changeset latestChg = GetLatestChangeForProj(vcs, proj.ServerItem);
                        if (null != latestChg && latestChg.ChangesetId == latest)
                        {
                            lock (m_newChangeSets)
                                m_newChangeSets.Add(latestChg);

                            this.DoInvoke(UpdateChangeSetListView);
                        }
                    }
                }

                m_stopFlag.Reset();
                m_workingFlag.Reset();
            }
        }


        private Changeset GetLatestChangeForProj(VersionControlServer vcs, string projPath)
        {
            Changeset latest = null;
            IEnumerable results = vcs.QueryHistory(
                projPath,
                VersionSpec.Latest,
                0,
                RecursionType.Full,
                null,
                null,
                null,
                1,
                true,
                true);

            foreach (object obj in results)
            {
                Changeset set = obj as Changeset;
                if (null == set)
                    continue;

                latest = set;
            }

            return latest;
        }


        /// <summary>
        /// Refreshes the list view with the current list 
        /// of changesets logged since bugging started.
        /// </summary>
        private void UpdateChangeSetListView()
        {
            this.lstCheckIns.Items.Clear();
            using (WaitScope wait = new WaitScope(this))
            {
                lock (m_newChangeSets)
                {
                    var sorted = from s in m_newChangeSets
                                 orderby s.CreationDate descending 
                                 select new ListViewItem(
                                    new string[]
                                     {
                                         s.ChangesetId.ToString(),
                                         s.CreationDate.ToString(),
                                         s.Committer,
                                         s.Comment,
                                         s.Changes.MapToString("; ", c => string.Format("[Type]:{0}, [Item]:{1}", c.ChangeType, c.Item.ServerItem))
                                     });

                    this.lstCheckIns.Items.AddRange(sorted.ToArray());
                } 
            }
        }
        #endregion
    }
}
